﻿using System;
using System.Collections.Generic;
using System.Reflection;
using MugenInjection.Interface;

namespace MugenInjection.Infrastructure
{
    /// <summary>
    ///     The default context for resolve binding.
    /// </summary>
    public sealed class BindingContext : IBindingContext, IEquatable<IBindingContext>
    {
        #region Equality members

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        /// <returns>
        /// true if the current object is equal to the <paramref name="other"/> parameter; otherwise, false.
        /// </returns>
        /// <param name="other">An object to compare with this object.
        ///                 </param>
        public bool Equals(IBindingContext other)
        {
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
            return _service == other.Service && Equals(_member, other.Member) &&
                   Equals(_parameterInfo, other.ParameterInfo);
        }

        /// <summary>
        /// Determines whether the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// true if the specified <see cref="T:System.Object"/> is equal to the current <see cref="T:System.Object"/>; otherwise, false.
        /// </returns>
        /// <param name="obj">The <see cref="T:System.Object"/> to compare with the current <see cref="T:System.Object"/>. 
        ///                 </param><exception cref="T:System.NullReferenceException">The <paramref name="obj"/> parameter is null.
        ///                 </exception>
        public override bool Equals(object obj)
        {
            var context = obj as IBindingContext;
            if (context == null)
                return false;
            return Equals(context);
            /*if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            return obj is BindingContext && Equals((BindingContext)obj);*/
        }

        /// <summary>
        /// Serves as a hash function for a particular type. 
        /// </summary>
        /// <returns>
        /// A hash code for the current <see cref="T:System.Object"/>.
        /// </returns>
        public override int GetHashCode()
        {
            unchecked
            {
                var hashCode = (_service != null ? _service.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (_member != null ? _member.GetHashCode() : 0);
                hashCode = (hashCode * 397) ^ (_parameterInfo != null ? _parameterInfo.GetHashCode() : 0);
                return hashCode;
            }
        }

        #endregion

        #region Fields

        private Type _typeInto;
        private Type _service;
        private MemberInfo _member;
        private ParameterInfo _parameterInfo;
        private IList<IInjectionParameter> _parameters;
        private IDictionary<string, object> _specialParameters;
        private IInjector _callInjector;
        private IInjector _injector;
        private IBindingContext _parentBindingContext;

        #endregion

        #region Constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="BindingContext"/> class.
        /// </summary>
        internal BindingContext(IBindingContext bindingContext)
        {
            _service = bindingContext.Service;
            _typeInto = null;
            _member = bindingContext.Member;
            _parameterInfo = bindingContext.ParameterInfo;
            _injector = bindingContext.Injector;
            _callInjector = bindingContext.CallInjector;
            _parentBindingContext = bindingContext.ParentBindingContext;
            if (Equals(bindingContext.Parameters, InjectorUtilsInternal.EmptyInjectionParams))
                _parameters = InjectorUtilsInternal.EmptyInjectionParams;
            else
                _parameters = new List<IInjectionParameter>(bindingContext.Parameters);
            if (Equals(bindingContext.SpecialParameters, InjectorUtilsInternal.ReadOnlyEmptyDictionary))
                _specialParameters = InjectorUtilsInternal.ReadOnlyEmptyDictionary;
            else
                _specialParameters = new Dictionary<string, object>(bindingContext.SpecialParameters);
            Binding = bindingContext.Binding;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BindingContext"/> class.
        /// </summary>
        internal BindingContext(Type service, MemberInfo member, ParameterInfo parameterInfo, Type typeInto, IInjector commonInjector, IBindingContext parentBindingContext)
        {
            _service = service;
            _member = member;
            _parameterInfo = parameterInfo;
            _injector = commonInjector;
            _callInjector = commonInjector;
            _typeInto = typeInto;
            _parameters = InjectorUtilsInternal.EmptyInjectionParams;
            _specialParameters = InjectorUtilsInternal.ReadOnlyEmptyDictionary;
            _parentBindingContext = parentBindingContext;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="BindingContext"/> class.
        /// </summary>
        public BindingContext(Type service, MemberInfo member, ParameterInfo parameterInfo, IInjector injector,
                              IInjector callInjector, IList<IInjectionParameter> parameters,
                              IDictionary<string, object> specialParameters)
        {
            Validate.ArgumentNotNull(service, "service");
            Validate.ArgumentNotNull(injector, "injector");
            Validate.ArgumentNotNull(callInjector, "callInjector");
            Validate.ArgumentNotNull(parameters, "parameters");
            Validate.ArgumentNotNull(specialParameters, "specialParameters");
            _service = service;
            _typeInto = null;
            _member = member;
            _injector = injector;
            _callInjector = callInjector;
            _parameters = parameters;
            _specialParameters = specialParameters;
            _parameterInfo = parameterInfo;
        }

        #endregion

        #region Implementation of IBindingContext

        /// <summary>
        ///     Gets or sets the service type.
        /// </summary>
        public Type Service
        {
            get { return _service; }
            set
            {
                Validate.PropertyNotNull(value, "Service");
                _service = value;
            }
        }

        /// <summary>
        ///     Gets the type into injected specified service <see cref="IBindingContext.Service" />.
        /// </summary>
        public Type TypeInto
        {
            get
            {
                if (_typeInto == null && Member != null)
#if NETFX_CORE
                    if (_member.GetMemberType() == MemberTypes.TypeInfo)
                        _typeInto = ((TypeInfo)_member).AsType();
                    else
                        _typeInto = _member.DeclaringType;
#else
                    if (_member.MemberType == MemberTypes.TypeInfo || _member.MemberType == MemberTypes.NestedType)
                        _typeInto = (Type)_member;
                    else
                        _typeInto = _member.DeclaringType;
#endif
                return _typeInto;
            }
        }

        /// <summary>
        ///     Gets the <see cref="MemberInfo" /> that will receive the injection, if any.
        /// </summary>
        public MemberInfo Member
        {
            get { return _member; }
            set { _member = value; }
        }

        /// <summary>
        ///     Gets the <see cref="IBindingContext.ParameterInfo" /> that will receive the injection, if any.
        /// </summary>
        public ParameterInfo ParameterInfo
        {
            get { return _parameterInfo; }
            set { _parameterInfo = value; }
        }

        /// <summary>
        ///     Gets the binding for activate, if any.
        /// </summary>
        public IBinding Binding { get; set; }

        /// <summary>
        ///     Gets the <see cref="IInjector" /> to activate current context.
        /// </summary>
        public IInjector Injector
        {
            get { return _injector; }
            set { _injector = value; }
        }

        /// <summary>
        ///     Gets the <see cref="IBindingContext.Injector" /> who called service activation.
        /// </summary>
        public IInjector CallInjector
        {
            get { return _callInjector; }
            set { _callInjector = value; }
        }

        /// <summary>
        ///     Gets the parameters.
        /// </summary>
        public IList<IInjectionParameter> Parameters
        {
            get { return _parameters; }
            set { _parameters = value; }
        }

        /// <summary>
        ///     Gets the special parameters.
        /// </summary>
        public IDictionary<string, object> SpecialParameters
        {
            get { return _specialParameters; }
            set { _specialParameters = value; }
        }

        /// <summary>
        ///     Gets or sets the parent binding context.
        /// </summary>
        public IBindingContext ParentBindingContext
        {
            get { return _parentBindingContext; }
            set { _parentBindingContext = value; }
        }

        /// <summary>
        /// Updates the current <c>IBindingContext</c>.
        /// </summary>
        public void Update(Type service, MemberInfo member, ParameterInfo parameterInfo, IInjector injector, IInjector callInjector,
            IList<IInjectionParameter> parameters, IDictionary<string, object> specialParameters)
        {
            Validate.ArgumentNotNull(service, "service");
            Validate.ArgumentNotNull(injector, "injector");
            Validate.ArgumentNotNull(callInjector, "callInjector");
            Validate.ArgumentNotNull(parameters, "parameters");
            Validate.ArgumentNotNull(specialParameters, "specialParameters");
            _service = service;
            _typeInto = null;
            _member = member;
            _injector = injector;
            _callInjector = callInjector;
            _parameters = parameters;
            _specialParameters = specialParameters;
            _parameterInfo = parameterInfo;
        }

        /// <summary>
        /// Updates the current <see cref="IBindingContext"/>.
        /// </summary>
        /// <param name="callInjector">The specified call injector.</param>
        /// <param name="injector">The specified injector.</param>
        public void Update(IInjector callInjector, IInjector injector)
        {
            _callInjector = callInjector;
            _injector = injector;
        }

        /// <summary>
        ///     Gets the hash-code of values in the binding-context.
        /// </summary>
        /// <returns>The hash code of values.</returns>
        int IBindingContext.GetSnapshot()
        {
            unchecked
            {
                int value = 0;
                if (_service != null)
                    value = _service.GetHashCode();

                value = value * 397;
                if (TypeInto != null)
                    value = value ^ TypeInto.GetHashCode();

                value = value * 397;
                if (_member != null)
                    value = value ^ _member.GetHashCode();

                value = value * 397;
                if (_parameterInfo != null)
                    value = value ^ _parameterInfo.GetHashCode();

                value = value * 397;
                object namedValue;
                if (_specialParameters.TryGetValue(SpecialParameterKeys.KeyedBindingParameter, out namedValue))
                    value = value ^ namedValue.GetHashCode();
                return value;
            }
        }

        #endregion

        #region Overrides of Object

        /// <summary>
        /// Returns a <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </summary>
        /// <returns>
        /// A <see cref="T:System.String"/> that represents the current <see cref="T:System.Object"/>.
        /// </returns>
        public override string ToString()
        {
            return InjectorUtilsInternal.GetBuildPath(this);
        }

        #endregion
    }
}